{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Private Predictions with TFE Keras"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Step 1: Public Training without Differential Privacy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To learn about the overal objectives of this tutorial, you can first go through the notebook `a - Public Training without DP`. It's exactly the same objectives except that we will train the model with differential privacy.\n",
    "\n",
    "By training your model with differential privacy, you can ensure that the model is not memorizing sensitive informations about the training set. If the model is not trained with differential privacy, attackers could reveal some private imformations by just querring the deployed model. Two common attacks are [membership inference](https://www.cs.cornell.edu/~shmat/shmat_oak17.pdf) and [model inversion](https://www.cs.cmu.edu/~mfredrik/papers/fjr2015ccs.pdf).\n",
    "\n",
    "To learn mode about [TensorFlow Privacy](https://github.com/tensorflow/privacy) you can read this [excellent blog post](http://www.cleverhans.io/privacy/2019/03/26/machine-learning-with-differential-privacy-in-tensorflow.html). Before running this notebook, please install [TensorFlow Privacy](https://github.com/tensorflow/privacy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"Training a CNN on MNIST with Keras and the DP SGD optimizer.\n",
    "source: https://github.com/tensorflow/privacy/blob/master/tutorials/mnist_dpsgd_tutorial_keras.py\n",
    "\"\"\"\n",
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "from privacy.analysis.rdp_accountant import compute_rdp\n",
    "from privacy.analysis.rdp_accountant import get_privacy_spent\n",
    "from privacy.optimizers.dp_optimizer import DPGradientDescentGaussianOptimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "W0812 16:43:41.723454 4616672704 deprecation.py:506] From /anaconda3/envs/pysyft_36/lib/python3.6/site-packages/tensorflow/python/ops/init_ops.py:1251: calling VarianceScaling.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Call initializer instance with the dtype argument instead of passing it to the constructor\n",
      "W0812 16:43:42.004826 4616672704 deprecation.py:323] From /anaconda3/envs/pysyft_36/lib/python3.6/site-packages/tensorflow/python/ops/clip_ops.py:286: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.where in 2.0, which has the same broadcast rule as np.where\n",
      "W0812 16:43:42.030165 4616672704 deprecation_wrapper.py:119] From /Users/yanndupis/Documents/dropoutlabs/privacy/privacy/dp_query/gaussian_query.py:101: The name tf.random_normal is deprecated. Please use tf.random.normal instead.\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 60000 samples, validate on 10000 samples\n",
      "Epoch 1/20\n",
      "60000/60000 [==============================] - 163s 3ms/sample - loss: 1.3740 - acc: 0.5710 - val_loss: 0.7501 - val_acc: 0.7694\n",
      "Epoch 2/20\n",
      "60000/60000 [==============================] - 173s 3ms/sample - loss: 0.6540 - acc: 0.8002 - val_loss: 0.5825 - val_acc: 0.8213\n",
      "Epoch 3/20\n",
      "60000/60000 [==============================] - 212s 4ms/sample - loss: 0.5638 - acc: 0.8326 - val_loss: 0.5217 - val_acc: 0.8486\n",
      "Epoch 4/20\n",
      "60000/60000 [==============================] - 226s 4ms/sample - loss: 0.4947 - acc: 0.8555 - val_loss: 0.4364 - val_acc: 0.8750\n",
      "Epoch 5/20\n",
      "60000/60000 [==============================] - 223s 4ms/sample - loss: 0.4756 - acc: 0.8684 - val_loss: 0.4666 - val_acc: 0.8834\n",
      "Epoch 6/20\n",
      "60000/60000 [==============================] - 196s 3ms/sample - loss: 0.4429 - acc: 0.8806 - val_loss: 0.3911 - val_acc: 0.8983\n",
      "Epoch 7/20\n",
      "60000/60000 [==============================] - 181s 3ms/sample - loss: 0.3975 - acc: 0.8966 - val_loss: 0.3743 - val_acc: 0.9059\n",
      "Epoch 8/20\n",
      "60000/60000 [==============================] - 169s 3ms/sample - loss: 0.3702 - acc: 0.9039 - val_loss: 0.3452 - val_acc: 0.9150\n",
      "Epoch 9/20\n",
      "60000/60000 [==============================] - 132s 2ms/sample - loss: 0.3476 - acc: 0.9115 - val_loss: 0.3126 - val_acc: 0.9221\n",
      "Epoch 10/20\n",
      "60000/60000 [==============================] - 130s 2ms/sample - loss: 0.3345 - acc: 0.9175 - val_loss: 0.2846 - val_acc: 0.9301\n",
      "Epoch 11/20\n",
      "60000/60000 [==============================] - 136s 2ms/sample - loss: 0.3201 - acc: 0.9215 - val_loss: 0.2890 - val_acc: 0.9285\n",
      "Epoch 12/20\n",
      "60000/60000 [==============================] - 132s 2ms/sample - loss: 0.3139 - acc: 0.9257 - val_loss: 0.2717 - val_acc: 0.9355\n",
      "Epoch 13/20\n",
      "60000/60000 [==============================] - 129s 2ms/sample - loss: 0.3037 - acc: 0.9282 - val_loss: 0.2827 - val_acc: 0.9311\n",
      "Epoch 14/20\n",
      "60000/60000 [==============================] - 143s 2ms/sample - loss: 0.2941 - acc: 0.9317 - val_loss: 0.2581 - val_acc: 0.9372\n",
      "Epoch 15/20\n",
      "60000/60000 [==============================] - 135s 2ms/sample - loss: 0.2983 - acc: 0.9318 - val_loss: 0.2765 - val_acc: 0.9360\n",
      "Epoch 16/20\n",
      "60000/60000 [==============================] - 132s 2ms/sample - loss: 0.2893 - acc: 0.9344 - val_loss: 0.2360 - val_acc: 0.9410\n",
      "Epoch 17/20\n",
      "60000/60000 [==============================] - 125s 2ms/sample - loss: 0.2857 - acc: 0.9368 - val_loss: 0.2725 - val_acc: 0.9381\n",
      "Epoch 18/20\n",
      "60000/60000 [==============================] - 136s 2ms/sample - loss: 0.2982 - acc: 0.9387 - val_loss: 0.2639 - val_acc: 0.9423\n",
      "Epoch 19/20\n",
      "60000/60000 [==============================] - 131s 2ms/sample - loss: 0.3051 - acc: 0.9397 - val_loss: 0.2689 - val_acc: 0.9395\n",
      "Epoch 20/20\n",
      "60000/60000 [==============================] - 140s 2ms/sample - loss: 0.3092 - acc: 0.9388 - val_loss: 0.2546 - val_acc: 0.9450\n",
      "For delta=1e-5, the current epsilon is: 1.76\n"
     ]
    }
   ],
   "source": [
    "dpsgd = True            # If True, train with DP-SGD\n",
    "learning_rate = 0.15    # Learning rate for training\n",
    "noise_multiplier = 1.1  # Ratio of the standard deviation to the clipping norm\n",
    "l2_norm_clip = 1.0      # Clipping norm\n",
    "batch_size = 250        # Batch size\n",
    "epochs = 20             # Number of epochs\n",
    "microbatches = 50       # Number of microbatches\n",
    "\n",
    "\n",
    "def compute_epsilon(steps):\n",
    "    \"\"\"Computes epsilon value for given hyperparameters.\"\"\"\n",
    "    if noise_multiplier == 0.0:\n",
    "        return float('inf')\n",
    "    orders = [1 + x / 10. for x in range(1, 100)] + list(range(12, 64))\n",
    "    sampling_probability = batch_size / 60000\n",
    "    rdp = compute_rdp(q=sampling_probability,\n",
    "                    noise_multiplier=noise_multiplier,\n",
    "                    steps=steps,\n",
    "                    orders=orders)\n",
    "    # Delta is set to 1e-5 because MNIST has 60000 training points.\n",
    "    return get_privacy_spent(orders, rdp, target_delta=1e-5)[0]\n",
    "\n",
    "\n",
    "def load_mnist():\n",
    "    \"\"\"Loads MNIST and preprocesses to combine training and validation data.\"\"\"\n",
    "    train, test = tf.keras.datasets.mnist.load_data()\n",
    "    train_data, train_labels = train\n",
    "    test_data, test_labels = test\n",
    "\n",
    "    train_data = np.array(train_data, dtype=np.float32) / 255\n",
    "    test_data = np.array(test_data, dtype=np.float32) / 255\n",
    "\n",
    "    train_data = train_data.reshape(train_data.shape[0], 28, 28, 1)\n",
    "    test_data = test_data.reshape(test_data.shape[0], 28, 28, 1)\n",
    "\n",
    "    train_labels = np.array(train_labels, dtype=np.int32)\n",
    "    test_labels = np.array(test_labels, dtype=np.int32)\n",
    "    \n",
    "    train_labels = tf.keras.utils.to_categorical(train_labels, num_classes=10)\n",
    "    test_labels = tf.keras.utils.to_categorical(test_labels, num_classes=10)\n",
    "\n",
    "    assert train_data.min() == 0.\n",
    "    assert train_data.max() == 1.\n",
    "    assert test_data.min() == 0.\n",
    "    assert test_data.max() == 1.\n",
    "\n",
    "    return train_data, train_labels, test_data, test_labels\n",
    "\n",
    "tf.logging.set_verbosity(tf.logging.INFO)\n",
    "if dpsgd and batch_size % microbatches != 0:\n",
    "    raise ValueError('Number of microbatches should divide evenly batch_size')\n",
    "\n",
    "# Load training and test data.\n",
    "train_data, train_labels, test_data, test_labels = load_mnist()\n",
    "\n",
    "model = tf.keras.Sequential([\n",
    "      tf.keras.layers.Conv2D(16, 8,\n",
    "                             strides=2,\n",
    "                             padding='same',\n",
    "                             activation='relu',\n",
    "                             input_shape=(28, 28, 1)),\n",
    "      tf.keras.layers.AveragePooling2D(2, 1),\n",
    "      tf.keras.layers.Conv2D(32, 4,\n",
    "                             strides=2,\n",
    "                             padding='valid',\n",
    "                             activation='relu'),\n",
    "      tf.keras.layers.AveragePooling2D(2, 1),\n",
    "      tf.keras.layers.Flatten(),\n",
    "      tf.keras.layers.Dense(32, activation='relu'),\n",
    "      tf.keras.layers.Dense(10)\n",
    "  ])\n",
    "\n",
    "if dpsgd:\n",
    "    optimizer = DPGradientDescentGaussianOptimizer(\n",
    "        l2_norm_clip=l2_norm_clip,\n",
    "        noise_multiplier=noise_multiplier,\n",
    "        num_microbatches=microbatches,\n",
    "        learning_rate=learning_rate)\n",
    "    # Compute vector of per-example loss rather than its mean over a minibatch.\n",
    "    loss = tf.keras.losses.CategoricalCrossentropy(\n",
    "        from_logits=True, reduction=tf.losses.Reduction.NONE)\n",
    "else:\n",
    "    optimizer = tf.optimizers.SGD(learning_rate=learning_rate)\n",
    "    loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)\n",
    "\n",
    "# Compile model with Keras\n",
    "model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])\n",
    "\n",
    "# Train model with Keras\n",
    "model.fit(train_data, train_labels,\n",
    "        epochs=epochs,\n",
    "        validation_data=(test_data, test_labels),\n",
    "        batch_size=batch_size)\n",
    "\n",
    "# Compute the privacy budget expended.\n",
    "if dpsgd:\n",
    "    eps = compute_epsilon(epochs * 60000 // batch_size)\n",
    "    print('For delta=1e-5, the current epsilon is: %.2f' % eps)\n",
    "else:\n",
    "    print('Trained with vanilla non-private SGD optimizer')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.save('short-dnn.h5')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
